﻿namespace System.ServiceModel.DomainServices.Client
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.ServiceModel.DomainServices;
    using System.Threading;

    public abstract class DomainContext : INotifyPropertyChanged
    {
        private int _activeLoadCount;
        private System.ServiceModel.DomainServices.Client.DomainClient _domainClient;
        private System.ServiceModel.DomainServices.Client.EntityContainer _entityContainer;
        private bool _isSubmitting;
        private SynchronizationContext _syncContext;
        private object _syncRoot = new object();
        private System.ComponentModel.DataAnnotations.ValidationContext _validationContext;
        private static MethodInfo createInvokeOperationMethod = typeof(System.ServiceModel.DomainServices.Client.InvokeOperation).GetMethod("Create", BindingFlags.NonPublic | BindingFlags.Static);
        private static MethodInfo createLoadOperationMethod = typeof(LoadOperation).GetMethod("Create", BindingFlags.NonPublic | BindingFlags.Static);
        private PropertyChangedEventHandler propertyChanged;
        private Dictionary<string, bool> requiresValidationMap = new Dictionary<string, bool>();
        internal const int TotalCountUndefined = -1;

        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                PropertyChangedEventHandler handler2;
                PropertyChangedEventHandler propertyChanged = this.propertyChanged;
                do
                {
                    handler2 = propertyChanged;
                    PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler) Delegate.Combine(handler2, value);
                    propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);
                }
                while (propertyChanged != handler2);
            }
            remove
            {
                PropertyChangedEventHandler handler2;
                PropertyChangedEventHandler propertyChanged = this.propertyChanged;
                do
                {
                    handler2 = propertyChanged;
                    PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler) Delegate.Remove(handler2, value);
                    propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);
                }
                while (propertyChanged != handler2);
            }
        }

        protected DomainContext(System.ServiceModel.DomainServices.Client.DomainClient domainClient)
        {
            if (domainClient == null)
            {
                throw new ArgumentNullException("domainClient");
            }
            this._domainClient = domainClient;
            this._syncContext = SynchronizationContext.Current ?? new SynchronizationContext();
        }

        private static void AddEntityErrors(Entity failedEntity, IEnumerable<ValidationResultInfo> errors, Dictionary<Entity, List<ValidationResult>> entityErrorMap)
        {
            List<ValidationResult> list = null;
            if (!entityErrorMap.TryGetValue(failedEntity, out list))
            {
                list = (from e in errors select new ValidationResult(e.Message, e.SourceMemberNames)).ToList<ValidationResult>();
                entityErrorMap[failedEntity] = list;
                ValidationUtilities.ApplyValidationErrors(failedEntity, list);
            }
            else
            {
                foreach (ValidationResultInfo info in errors)
                {
                    ValidationResult result = new ValidationResult(info.Message, info.SourceMemberNames);
                    if (!list.Contains<ValidationResult>(result, new ValidationResultEqualityComparer()))
                    {
                        list.Add(result);
                    }
                }
            }
        }

        public void AddReference(Type entityType, DomainContext domainContext)
        {
            if (entityType == null)
            {
                throw new ArgumentNullException("entityType");
            }
            if (domainContext == null)
            {
                throw new ArgumentNullException("domainContext");
            }
            EntitySet entitySet = domainContext.EntityContainer.GetEntitySet(entityType);
            this.EntityContainer.AddReference(entitySet);
        }

        private static void ApplyMemberSynchronizations(IEnumerable<ChangeSetEntry> changeSetResults)
        {
            foreach (ChangeSetEntry entry in from p in changeSetResults
                where p.Operation != EntityOperationType.Delete
                select p)
            {
                if (entry.Entity != null)
                {
                    entry.ClientEntity.Merge(entry.Entity, LoadBehavior.RefreshCurrent);
                }
            }
        }

        private void CompleteInvoke(IAsyncResult asyncResult)
        {
            System.ServiceModel.DomainServices.Client.InvokeOperation asyncState = (System.ServiceModel.DomainServices.Client.InvokeOperation) asyncResult.AsyncState;
            if (!asyncState.IsCanceled)
            {
                Exception error = null;
                InvokeCompletedResult result = null;
                try
                {
                    result = this.DomainClient.EndInvoke(asyncResult);
                }
                catch (Exception exception2)
                {
                    if (exception2.IsFatal())
                    {
                        throw;
                    }
                    error = exception2;
                }
                if (error == null)
                {
                    if (result.ValidationErrors.Any<ValidationResult>())
                    {
                        asyncState.Complete(result.ValidationErrors);
                    }
                    else
                    {
                        DomainClientResult result2 = DomainClientResult.CreateInvokeResult(result.ReturnValue, result.ValidationErrors);
                        asyncState.Complete(result2);
                    }
                }
                else
                {
                    asyncState.Complete(error);
                }
            }
        }

        private void CompleteLoad(IAsyncResult asyncResult)
        {
            LoadOperation asyncState = (LoadOperation) asyncResult.AsyncState;
            IEnumerable<Entity> first = null;
            IEnumerable<Entity> allEntities = null;
            int totalEntityCount = -1;
            if (!asyncState.IsCanceled)
            {
                Exception error = null;
                QueryCompletedResult result = null;
                try
                {
                    lock (this._syncRoot)
                    {
                        result = this.DomainClient.EndQuery(asyncResult);
                        first = this.EntityContainer.LoadEntities(result.Entities, asyncState.LoadBehavior).Cast<Entity>();
                        IEnumerable<Entity> second = this.EntityContainer.LoadEntities(result.IncludedEntities, asyncState.LoadBehavior).Cast<Entity>();
                        allEntities = first.Concat<Entity>(second);
                        totalEntityCount = result.TotalCount;
                    }
                }
                catch (Exception exception2)
                {
                    if (exception2.IsFatal())
                    {
                        throw;
                    }
                    error = exception2;
                }
                finally
                {
                    this.DecrementLoadCount();
                }
                if (error == null)
                {
                    if (result.ValidationErrors.Any<ValidationResult>())
                    {
                        asyncState.Complete(result.ValidationErrors);
                    }
                    else
                    {
                        DomainClientResult result2 = DomainClientResult.CreateQueryResult(first, allEntities, totalEntityCount, result.ValidationErrors);
                        asyncState.Complete(result2);
                    }
                }
                else
                {
                    asyncState.Complete(error);
                }
            }
        }

        private void CompleteSubmitChanges(IAsyncResult asyncResult)
        {
            SubmitOperation asyncState = (SubmitOperation) asyncResult.AsyncState;
            IEnumerable<ChangeSetEntry> results = null;
            Exception error = null;
            try
            {
                foreach (Entity entity in (IEnumerable<Entity>) asyncState.ChangeSet)
                {
                    entity.IsSubmitting = false;
                }
                if (asyncState.IsCanceled)
                {
                    return;
                }
                SubmitCompletedResult result = this.DomainClient.EndSubmit(asyncResult);
                ProcessSubmitResults(result.ChangeSet, result.Results.Cast<ChangeSetEntry>());
                results = result.Results;
            }
            catch (Exception exception2)
            {
                if (exception2.IsFatal())
                {
                    throw;
                }
                error = exception2;
            }
            finally
            {
                this.IsSubmitting = false;
            }
            if (!asyncState.IsCanceled)
            {
                if (error == null)
                {
                    if (!Enumerable.Any<ChangeSetEntry>(results, (Func<ChangeSetEntry, bool>) (p => p.HasError)))
                    {
                        asyncState.Complete();
                    }
                    else if (Enumerable.Any<ChangeSetEntry>(results, (Func<ChangeSetEntry, bool>) (p => ((p.ValidationErrors != null) && p.ValidationErrors.Any<ValidationResultInfo>()))))
                    {
                        asyncState.Complete(OperationErrorStatus.ValidationFailed);
                    }
                    else if (Enumerable.Any<ChangeSetEntry>(results, (Func<ChangeSetEntry, bool>) (p => p.HasConflict)))
                    {
                        asyncState.Complete(OperationErrorStatus.Conflicts);
                    }
                }
                else
                {
                    asyncState.Complete(error);
                }
            }
        }

        protected abstract System.ServiceModel.DomainServices.Client.EntityContainer CreateEntityContainer();
        [EditorBrowsable(EditorBrowsableState.Never)]
        protected EntityQuery<TEntity> CreateQuery<TEntity>(string queryName, IDictionary<string, object> parameters, bool hasSideEffects, bool isComposable) where TEntity: Entity
        {
            return new EntityQuery<TEntity>(this.DomainClient, queryName, parameters, hasSideEffects, isComposable);
        }

        private void DecrementLoadCount()
        {
            Interlocked.Decrement(ref this._activeLoadCount);
            if (this._activeLoadCount == 0)
            {
                this.RaisePropertyChanged("IsLoading");
            }
        }

        private void EntityContainerPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (string.CompareOrdinal(e.PropertyName, "HasChanges") == 0)
            {
                this.RaisePropertyChanged("HasChanges");
            }
        }

        private void IncrementLoadCount()
        {
            Interlocked.Increment(ref this._activeLoadCount);
            if (this._activeLoadCount == 1)
            {
                this.RaisePropertyChanged("IsLoading");
            }
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public virtual InvokeOperation<TValue> InvokeOperation<TValue>(string operationName, Type returnType, IDictionary<string, object> parameters, bool hasSideEffects, Action<InvokeOperation<TValue>> callback, object userState)
        {
            Action<InvokeOperation<TValue>> action2 = null;
            if (string.IsNullOrEmpty(operationName))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.Parameter_NullOrEmpty, new object[] { "operationName" }));
            }
            if (returnType == null)
            {
                throw new ArgumentNullException("returnType");
            }
            IAsyncResult result = null;
            Action<InvokeOperation<TValue>> cancelAction = null;
            if (this.DomainClient.SupportsCancellation)
            {
                if (action2 == null)
                {
                    action2 = delegate (InvokeOperation<TValue> op) {
                        if (result != null)
                        {
                            this.DomainClient.CancelInvoke(result);
                        }
                    };
                }
                cancelAction = action2;
            }
            InvokeOperation<TValue> operation = new InvokeOperation<TValue>(operationName, parameters, callback, userState, cancelAction);
            InvokeArgs invokeArgs = new InvokeArgs(operationName, returnType, parameters, hasSideEffects);
            result = this.DomainClient.BeginInvoke(invokeArgs, delegate (IAsyncResult asyncResult) {
                this._syncContext.Post(delegate {
                    this.CompleteInvoke(asyncResult);
                }, null);
            }, operation);
            return operation;
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public virtual System.ServiceModel.DomainServices.Client.InvokeOperation InvokeOperation(string operationName, Type returnType, IDictionary<string, object> parameters, bool hasSideEffects, Action<System.ServiceModel.DomainServices.Client.InvokeOperation> callback, object userState)
        {
            Action<System.ServiceModel.DomainServices.Client.InvokeOperation> action2 = null;
            if (string.IsNullOrEmpty(operationName))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.Parameter_NullOrEmpty, new object[] { "operationName" }));
            }
            if (returnType == null)
            {
                throw new ArgumentNullException("returnType");
            }
            IAsyncResult result = null;
            Action<System.ServiceModel.DomainServices.Client.InvokeOperation> action = null;
            if (this.DomainClient.SupportsCancellation)
            {
                if (action2 == null)
                {
                    action2 = delegate (System.ServiceModel.DomainServices.Client.InvokeOperation op) {
                        if (result != null)
                        {
                            this.DomainClient.CancelInvoke(result);
                        }
                    };
                }
                action = action2;
            }
            System.ServiceModel.DomainServices.Client.InvokeOperation operation = (System.ServiceModel.DomainServices.Client.InvokeOperation) createInvokeOperationMethod.MakeGenericMethod(new Type[] { (returnType == typeof(void)) ? typeof(object) : returnType }).Invoke(null, new object[] { operationName, parameters, callback, userState, action });
            InvokeArgs invokeArgs = new InvokeArgs(operationName, returnType, parameters, hasSideEffects);
            result = this.DomainClient.BeginInvoke(invokeArgs, delegate (IAsyncResult asyncResult) {
                this._syncContext.Post(delegate {
                    this.CompleteInvoke(asyncResult);
                }, null);
            }, operation);
            return operation;
        }

        public LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query) where TEntity: Entity
        {
            return (LoadOperation<TEntity>) this.Load(query, LoadBehavior.KeepCurrent, null, null);
        }

        public LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query, bool throwOnError) where TEntity: Entity
        {
            return this.Load<TEntity>(query, LoadBehavior.KeepCurrent, throwOnError);
        }

        public LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query, Action<LoadOperation<TEntity>> callback, object userState) where TEntity: Entity
        {
            return this.Load<TEntity>(query, LoadBehavior.KeepCurrent, callback, userState);
        }

        public LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query, LoadBehavior loadBehavior, bool throwOnError) where TEntity: Entity
        {
            Action<LoadOperation<TEntity>> callback = null;
            Action<LoadOperation<TEntity>> action2 = null;
            if (!throwOnError)
            {
                if (action2 == null)
                {
                    action2 = delegate (LoadOperation<TEntity> op) {
                        if (op.HasError)
                        {
                            op.MarkErrorAsHandled();
                        }
                    };
                }
                callback = action2;
            }
            return this.Load<TEntity>(query, loadBehavior, callback, null);
        }

        public LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query, LoadBehavior loadBehavior, Action<LoadOperation<TEntity>> callback, object userState) where TEntity: Entity
        {
            Action<LoadOperation> action2 = null;
            Action<LoadOperation> action = null;
            if (callback != null)
            {
                if (action2 == null)
                {
                    action2 = delegate (LoadOperation lo) {
                        callback((LoadOperation<TEntity>) lo);
                    };
                }
                action = action2;
            }
            return (LoadOperation<TEntity>) this.Load(query, loadBehavior, action, userState);
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public virtual LoadOperation Load(EntityQuery query, LoadBehavior loadBehavior, Action<LoadOperation> callback, object userState)
        {
            Action<LoadOperation> action2 = null;
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }
            if (this.DomainClient != query.DomainClient)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.DomainContext_InvalidEntityQueryDomainClient, new object[] { query.QueryName }));
            }
            IAsyncResult result = null;
            Action<LoadOperation> action = null;
            if (this.DomainClient.SupportsCancellation)
            {
                if (action2 == null)
                {
                    action2 = delegate (LoadOperation op) {
                        if (result != null)
                        {
                            this.DomainClient.CancelQuery(result);
                        }
                        this.DecrementLoadCount();
                    };
                }
                action = action2;
            }
            LoadOperation operation = (LoadOperation) createLoadOperationMethod.MakeGenericMethod(new Type[] { query.EntityType }).Invoke(null, new object[] { query, loadBehavior, callback, userState, action });
            this.IncrementLoadCount();
            result = this.DomainClient.BeginQuery(query, delegate (IAsyncResult asyncResult) {
                this._syncContext.Post(delegate {
                    this.CompleteLoad(asyncResult);
                }, null);
            }, operation);
            return operation;
        }

        private bool MethodRequiresValidation(string methodName, object[] paramValues)
        {
            bool flag = false;
            if (!this.requiresValidationMap.TryGetValue(methodName, out flag))
            {
                flag = ValidationUtilities.MethodRequiresValidation(ValidationUtilities.GetMethod(this, methodName, paramValues));
                this.requiresValidationMap[methodName] = flag;
            }
            return flag;
        }

        private static void ProcessSubmitResults(EntityChangeSet changeSet, IEnumerable<ChangeSetEntry> changeSetResults)
        {
            bool flag = false;
            Dictionary<Entity, List<ValidationResult>> entityErrorMap = new Dictionary<Entity, List<ValidationResult>>();
            foreach (ChangeSetEntry entry in changeSetResults)
            {
                if ((entry.ValidationErrors != null) && entry.ValidationErrors.Any<ValidationResultInfo>())
                {
                    flag = true;
                    AddEntityErrors(entry.ClientEntity, entry.ValidationErrors, entityErrorMap);
                }
                if (entry.HasConflict)
                {
                    EntityConflict conflict = new EntityConflict(entry.ClientEntity, entry.StoreEntity, entry.ConflictMembers, entry.IsDeleteConflict);
                    entry.ClientEntity.EntityConflict = conflict;
                    flag = true;
                }
            }
            if (!flag)
            {
                ApplyMemberSynchronizations(changeSetResults);
                foreach (Entity entity in Enumerable.Where<Entity>(changeSet.RemovedEntities, delegate (Entity p) {
                    if (p.Parent != null)
                    {
                        return p.Parent.EntityState != EntityState.Deleted;
                    }
                    return true;
                }))
                {
                    ((IChangeTracking) entity).AcceptChanges();
                }
                foreach (Entity entity2 in changeSet.AddedEntities.Concat<Entity>(changeSet.ModifiedEntities))
                {
                    ((IChangeTracking) entity2).AcceptChanges();
                }
            }
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentNullException("propertyName");
            }
            if (this.propertyChanged != null)
            {
                this.propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public void RejectChanges()
        {
            ((IRevertibleChangeTracking) this.EntityContainer).RejectChanges();
        }

        public SubmitOperation SubmitChanges()
        {
            return this.SubmitChanges(null, null);
        }

        public virtual SubmitOperation SubmitChanges(Action<SubmitOperation> callback, object userState)
        {
            AsyncCallback callback3 = null;
            SubmitOperation submitOperation;
            if (this.IsSubmitting)
            {
                throw new InvalidOperationException(Resource.DomainContext_SubmitAlreadyInProgress);
            }
            this.IsSubmitting = true;
            EntityChangeSet changeSet = null;
            try
            {
                Action<SubmitOperation> action2 = null;
                SendOrPostCallback d = null;
                changeSet = this.EntityContainer.GetChanges();
                bool validChangeset = this.ValidateChangeSet(changeSet, this.ValidationContext);
                IAsyncResult result = null;
                Action<SubmitOperation> cancelAction = null;
                if (this.DomainClient.SupportsCancellation)
                {
                    if (action2 == null)
                    {
                        action2 = delegate (SubmitOperation op) {
                            if (result != null)
                            {
                                this.DomainClient.CancelSubmit(result);
                            }
                            this.IsSubmitting = false;
                            foreach (Entity entity in (IEnumerable<Entity>) changeSet)
                            {
                                entity.IsSubmitting = false;
                            }
                        };
                    }
                    cancelAction = action2;
                }
                submitOperation = new SubmitOperation(changeSet, callback, userState, cancelAction);
                if (changeSet.IsEmpty || !validChangeset)
                {
                    if (d == null)
                    {
                        d = delegate {
                            this.IsSubmitting = false;
                            if (!submitOperation.IsComplete)
                            {
                                if (!validChangeset)
                                {
                                    submitOperation.Complete(OperationErrorStatus.ValidationFailed);
                                }
                                else
                                {
                                    submitOperation.Complete();
                                }
                            }
                        };
                    }
                    this._syncContext.Post(d, null);
                }
                else
                {
                    foreach (Entity entity in (IEnumerable<Entity>) changeSet)
                    {
                        entity.IsSubmitting = true;
                    }
                    if (callback3 == null)
                    {
                        callback3 = delegate (IAsyncResult asyncResult) {
                            this._syncContext.Post(delegate {
                                this.CompleteSubmitChanges(asyncResult);
                            }, null);
                        };
                    }
                    result = this.DomainClient.BeginSubmit(changeSet, callback3, submitOperation);
                }
            }
            catch
            {
                this.IsSubmitting = false;
                if (changeSet != null)
                {
                    foreach (Entity entity2 in (IEnumerable<Entity>) changeSet)
                    {
                        entity2.IsSubmitting = false;
                    }
                }
                throw;
            }
            return submitOperation;
        }

        private bool ValidateChangeSet(EntityChangeSet changeSet, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
        {
            if (!changeSet.Validate(validationContext))
            {
                return false;
            }
            foreach (Entity entity in changeSet.ModifiedEntities)
            {
                EntityAction action = entity.EntityActions.FirstOrDefault<EntityAction>();
                if (action != null)
                {
                    try
                    {
                        object[] parameters = new object[action.Parameters.Count<object>() + 1];
                        parameters[0] = entity;
                        int num = 1;
                        foreach (object obj2 in action.Parameters)
                        {
                            parameters[num++] = obj2;
                        }
                        ValidationUtilities.GetMethod(this, action.Name, parameters);
                        continue;
                    }
                    catch (MissingMethodException exception)
                    {
                        throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.DomainContext_NamedUpdateMethodDoesNotExist, new object[] { action.Name, entity.GetType(), base.GetType() }), exception);
                    }
                }
            }
            return true;
        }

        protected void ValidateMethod(string methodName, IDictionary<string, object> parameters)
        {
            if (string.IsNullOrEmpty(methodName))
            {
                throw new ArgumentNullException("methodName");
            }
            object[] paramValues = (parameters != null) ? parameters.Values.ToArray<object>() : new object[0];
            if (this.MethodRequiresValidation(methodName, paramValues))
            {
                System.ComponentModel.DataAnnotations.ValidationContext validationContext = ValidationUtilities.CreateValidationContext(this, this.ValidationContext);
                ValidationUtilities.ValidateMethodCall(methodName, validationContext, paramValues);
            }
        }

        public System.ServiceModel.DomainServices.Client.DomainClient DomainClient
        {
            get
            {
                if (this._domainClient.EntityTypes == null)
                {
                    this._domainClient.EntityTypes = from p in this.EntityContainer.EntitySets select p.EntityType;
                }
                return this._domainClient;
            }
        }

        public System.ServiceModel.DomainServices.Client.EntityContainer EntityContainer
        {
            get
            {
                if (this._entityContainer == null)
                {
                    this._entityContainer = this.CreateEntityContainer();
                    if (this._entityContainer == null)
                    {
                        throw new InvalidOperationException(Resource.DomainContext_EntityContainerCannotBeNull);
                    }
                    this._entityContainer.ValidationContext = this.ValidationContext;
                    this._entityContainer.PropertyChanged += new PropertyChangedEventHandler(this.EntityContainerPropertyChanged);
                }
                return this._entityContainer;
            }
        }

        public bool HasChanges
        {
            get
            {
                return this.EntityContainer.HasChanges;
            }
        }

        public bool IsLoading
        {
            get
            {
                return (this._activeLoadCount > 0);
            }
        }

        public bool IsSubmitting
        {
            get
            {
                return this._isSubmitting;
            }
            private set
            {
                this._isSubmitting = value;
                this.RaisePropertyChanged("IsSubmitting");
            }
        }

        public System.ComponentModel.DataAnnotations.ValidationContext ValidationContext
        {
            get
            {
                return this._validationContext;
            }
            set
            {
                this._validationContext = value;
                this.EntityContainer.ValidationContext = value;
            }
        }
    }
}

